package color;

import geom.Vector;

import java.util.ArrayList;
import java.util.Iterator;

import proxml.XMLElement;


public class Lab{ 
	public float L, a, b;
	public enum Metric{ DELTA_E_CMC, DELTA_E_1994, SQUARED, MANHATTAN; };
	public static Metric metric = Metric.DELTA_E_CMC;
	public Lab(Lab lab) { 
		this(lab.L, lab.a, lab.b); 
	}
	public Lab (float L, float a, float b){ 
		this.L = L; this.a = a;this.b = b; 
	}
	public Lab (Vector v){ 
		this(v.x, v.y, v.z);
	}
	
	public String toString(){ 
		return "Lab:{"+L+", "+a+", "+b+"}"; 
	}
	
	public float distanceTo(Lab lab, Metric metric){
		switch(metric){
		case DELTA_E_CMC: 	return deltaE_CMC(this, lab, 1, 1);
		case DELTA_E_1994: 	return deltaE_1994(this, lab);
		case SQUARED: 		return squared(this, lab);
		case MANHATTAN: 	return manhattan(this, lab);
		default: 			return Float.NaN;
		}
	}
	
	public static float deltaE_CMC(Lab l1, Lab l2, int l, int c){
		float dA = l1.a - l2.a;
		float dB = l1.b - l2.b;
		float dL = l1.L - l2.L;
		float c1 = (float) Math.sqrt(l1.a*l1.a + l1.b*l1.b);
		float c2 = (float) Math.sqrt(l2.a*l2.a + l2.b*l2.b);
		float dC = c1 - c2;
		float dH = (float) Math.sqrt(dA*dA + dB*dB + dC*dC);
		float sL = 0.511f;
		if(l1.L >= 16) sL = 0.040975f * l1.L / (1 + 0.01765f * l1.L);
		float sC = 0.0638f * c1 / (1 + 0.0131f * c1) + 0.638f;
		float h1 = (float) Math.atan2(l1.b, l1.a);
		float f = (float)Math.sqrt(Math.pow(c1, 4) / (Math.pow(c1, 4) + 1900));
		float t;
		if(h1 >= 164 && h1 < 345)
			t = 0.56f + (float)Math.abs(0.2 * Math.cos(h1 + 168));
		else t = 0.36f + (float)Math.abs(0.4 * Math.cos(h1 + 35));
		float sH = sC * (f * t + 1 - f);
		
		float dE = (float) 
			Math.sqrt(Math.pow(dL / (l * sL), 2) + 
					  Math.pow(dC / (c * sC), 2) +
					  Math.pow(dH / sH, 	  2));
		return dE;
	}
	
	public static float deltaE_1994(Lab l1, Lab l2){
		float c1 = (float) Math.sqrt(l1.a*l1.a + l1.b*l1.b);
		float c2 = (float) Math.sqrt(l2.a*l2.a + l2.b*l2.b);
		float sH = 1 + 0.015f * c1;
		float sC = 1 + 0.045f * c1;
		float dA = l1.a - l2.a;
		float dB = l1.b - l2.b;
		float dL = l1.L - l2.L;
		float dC = c1 - c2;
		float dH = (float) Math.sqrt(dA*dA + dB*dB + dC*dC);
		
		float dE = (float) 
		Math.sqrt(Math.pow(dL, 		2) + 
				  Math.pow(dC / sC, 2) +
				  Math.pow(dH / sH, 2));
		return dE;
	}
	
	public static Lab average(ArrayList<Lab> labs){
		Iterator<Lab> i = labs.iterator();
		Lab avg = new Lab(0, 0, 0);
		while(i.hasNext()){
			Lab next = i.next();
			avg.L += next.L;
			avg.a += next.a;
			avg.b += next.b;
		}
		avg.L /= (float)labs.size();
		avg.a /= (float)labs.size();
		avg.b /= (float)labs.size();
		return avg;
	}
	
	public static float squared(Lab l1, Lab l2){
		return (float)Math.sqrt(
			Math.pow(l1.L - l2.L, 2) +
			Math.pow(l1.a - l2.a, 2) +
			Math.pow(l1.b - l2.b, 2)
		);
	}
	
	public static float manhattan(Lab l1, Lab l2){
		return 
			Math.abs(l1.L - l2.L) +
			Math.abs(l1.a - l2.a) +
			Math.abs(l1.b - l2.b);
	}

	@Override
	public int hashCode() {
		final int PRIME = 31;
		int result = 1;
		result = PRIME * result + Float.floatToIntBits(L);
		result = PRIME * result + Float.floatToIntBits(a);
		result = PRIME * result + Float.floatToIntBits(b);
		return result;
	}
	
	public Vector getVector(){
		return new Vector(L, a, b);
	}
	
	public Vector directionVector(Lab to){
		float L = this.L - to.L;
		float a = this.a - to.a;
		float b = this.b - to.b;
		float abs = (float)Math.sqrt(L*L + a*a + b*b);
		return new Vector(L / abs, a / abs, b / abs);
	}
	
	public float getVectorPos(Vector v){
		return (L * v.x + a * v.y + b * v.z) / v.absLength();
	}
	
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		final Lab other = (Lab) obj;
		if (Float.floatToIntBits(L) != Float.floatToIntBits(other.L))
			return false;
		if (Float.floatToIntBits(a) != Float.floatToIntBits(other.a))
			return false;
		if (Float.floatToIntBits(b) != Float.floatToIntBits(other.b))
			return false;
		return true;
	}
	
	public XMLElement toXML(){
		XMLElement elem = new XMLElement("Lab");
		elem.addAttribute("L", L);
		elem.addAttribute("a", a);
		elem.addAttribute("b", b);
		return elem;
	}
	
	public Lab getOppositeBrightness(){
		return new Lab((L + 50f) % 100f, a, b);
	}

}
